package ru.nosport.matrixaria.core;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;

import java.util.*;
import java.io.*;


/**
 * User: vfabr
 * Date: 25.09.2006
 * Time: 15:52:14
 */
public class ModuleShortcut implements Externalizable {
	private Class instanceClass;
	private String instanceClassName;
	private String instanceName;
	private Map<String,String> dependences = new HashMap<String, String>(10, 1.7f);
	private Configuration config;
	private static final long serialVersionUID = 6533576457179844155L;

	private boolean onFly;
	private boolean autoRun;

	private volatile int hashCode;

	private int indexInConf;
	private String confSrc;

	public ModuleShortcut(String _xml, int _index, String _className) throws InitException {
		if(_xml == null){
			throw new IllegalArgumentException("_xml is null.");
		}
		if(_index < 0){
			throw new IllegalArgumentException("_index should be >= 0.");
		}
		if(_className == null){
			throw new IllegalArgumentException("_className is null.");
		}
		if(_xml.length() < 1){
			throw new IllegalArgumentException("_xml is empty.");
		}
		if(_className.length() < 1){
			throw new IllegalArgumentException("_className is empty.");
		}

		this.confSrc = _xml;
		this.indexInConf = _index;
		this.instanceClassName = _className;

		this.init();
	}

	private void init() throws InitException {
		HierarchicalConfiguration conf;
		try {
			conf = this.strToConf(this.confSrc);
			conf = conf.configurationAt("instance(" + this.indexInConf + ")");
			this.instanceName = conf.getString("[@name]");
			this.setInstanceClass(this.instanceClassName);
			this.makeMap(conf.subset("dependences"));
			this.config = conf.subset("parameters");
			this.config.addProperty("registryInstanceName", this.instanceName);

			this.autoRun = conf.getBoolean("[@autorun]");
			this.onFly = !this.autoRun && conf.getBoolean("[@onfly]");
		} catch (ConfigurationException e) {
			throw new InitException(e);
		} catch (NoSuchElementException e) {
			this.onFly = false;
		}
	}

	public ModuleShortcut() {
	}


	public boolean isOnFly(){
		return this.onFly;
	}

	public boolean isAutorun(){
		return this.autoRun;
	}

	private HierarchicalConfiguration strToConf(String _src) throws ConfigurationException {
		XMLConfiguration conf = new XMLConfiguration();
		conf.load(new ByteArrayInputStream(_src.getBytes()));

		return conf;
	}
	

	/**
	 * Returns class object for the instance.
	 *
	 * @return instance class object
	 */
	public Class getInstanceClass() {
		return this.instanceClass;
	}

	/**
	 * Returns configuration used in instance creation process.
	 *
	 * @return configuration object
	 */
	public Configuration getConfig() {
		return config;
	}

	/**
	 * Make map dependences.
	 *
	 * @param _config -
	 * @throws InitException -
	 */
	private void makeMap(Configuration _config) throws InitException {
		List deps = _config.getList("dependence[@name]");
		for (int i=0; i < deps.size(); i++) {
			String key = _config.getString("dependence(" + i + ")[@name]");
			String val = _config.getString("dependence(" + i + ")");
			if(! this.dependences.containsKey(key)) {
				this.dependences.put(key, val);
			} else {
				throw new InitException("Duplicate dependence name. Name - " + key + ", value - " + val);
			}
		}
	}

	/**
	 * Set instance name.
	 *
	 * @param _name instance name
	 * @throws InitException -
	 */
	private void setInstanceClass(String _name) throws InitException {
		try {
			this.instanceClass = Class.forName(_name);
		} catch(ClassNotFoundException _ex) {
			throw new InitException("Class " + _name + " not found.");
		}
	}

	/**
	 * Return instance name.
	 *
	 * @return instance name.
	 */
	public String getInstanceName() {
		return this.instanceName;
	}

	/**
	 *
	 * @return Map of dependences
	 */
	public Map<String, String> getDependences() {
		return this.dependences;
	}

	/**
	 * Checks whether module depends on another (with class check)
	 *
	 * @param key -
	 * @param value -
	 * @return true if module depenedces contains entry with the given key (instance name) and value (class name) 
	 */
	public boolean checkDependsOn(String key, String value){
		return this.dependences.containsKey(key) && this.dependences.get(key).equals(value);
	}

	@Override
	public int hashCode(){
		int result = this.hashCode;
		if(result == 0){
			result = 17;
	        result = 31 * result + this.dependences.hashCode();
			result = 31 * result + this.confSrc.hashCode();
			result = 31 * result + this.indexInConf;
			result = 31 * result + this.instanceName.hashCode();
			result = 31 * result + this.instanceClass.hashCode();			
			this.hashCode = result;
		}
	    return result;
	}

	/**
	 * Сравниваются по полям и исходной строчке.
	 *
	 * @param _o -
	 * @return -
	 */
	@Override
	public boolean equals(Object _o){
		if(_o == this){
			return true;
		}
		if (!(_o instanceof ModuleShortcut)){
			return false;
		}
		ModuleShortcut sc = (ModuleShortcut) _o;

		return sc.indexInConf == this.indexInConf
				&& sc.confSrc.equals(this.confSrc)
				&& sc.dependences.equals(this.dependences)
				&& sc.instanceName.equals(this.instanceName)
				&& sc.instanceClass.equals(this.instanceClass);
	}
	
	public void writeExternal(ObjectOutput _out) throws IOException {
		_out.writeInt(this.indexInConf);
		_out.writeUTF(this.instanceClassName);
		_out.writeUTF(this.confSrc);

	}

	public void readExternal(ObjectInput _in) throws IOException, ClassNotFoundException {
		this.indexInConf = _in.readInt();
		this.instanceClassName = _in.readUTF();
		this.confSrc = _in.readUTF();

		try {
			this.init();
		} catch (InitException e) {
			throw new IllegalStateException("Deserialize error: params (indexInConf - "+this.indexInConf+", instanceClassName - "+this.instanceClassName+", confSrc - "+this.confSrc+")", e);
		}
	}

	@Override
	public String toString(){
		return this.instanceClassName+"."+this.instanceName;
	}
}
